Completed
Branch master (d95141)
by greg
33:30 queued 31:46
created

Page.js ➔ ... ➔ ???   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 4
c 3
b 0
f 0
nc 3
dl 0
loc 14
rs 9.2
nop 1
1
import Handlebars from 'handlebars'
2
import path from 'path'
3
import fse from 'fs-extra'
4
5
import {
6
  cmsEditor,
7
  abeEngine,
8
  cmsData,
9
  cmsTemplates,
10
  config,
11
  Hooks,
12
  Manager
13
} from '../'
14
15
/**
16
 * Page class
17
 * manage HTML generation for page template
18
 */
19
export default class Page {
20
21
  /**
22
   * Create new page object
23
   * @param  {Object} params req.params from express route
0 ignored issues
show
Documentation introduced by
The parameter params does not exist. Did you maybe forget to remove this comment?
Loading history...
24
   * @param  {Object} i18n translation
0 ignored issues
show
Documentation introduced by
The parameter i18n does not exist. Did you maybe forget to remove this comment?
Loading history...
25
   * @param  {Function} callback 
0 ignored issues
show
Documentation introduced by
The parameter callback does not exist. Did you maybe forget to remove this comment?
Loading history...
26
   * @param  {Boolean} onlyHTML default = false, if true HTML content will contains abe attributes
27
   * @return {String} HTML page as string
28
   */
29
  constructor(templateId, template, json, onlyHTML = false) {
30
    // HOOKS beforePageJson
31
    json = Hooks.instance.trigger('beforePageJson', json)
32
33
    if(typeof Handlebars.templates[templateId] !== 'undefined' && 
34
        Handlebars.templates[templateId] !== null && 
35
        config.files.templates.precompile
36
      ){
37
38
      template = Handlebars.templates[templateId]
39
      this.html = template(json, {data: {intl: config.intlData}})
40
41
      //console.log('precompile')
42
43
    } else {
44
45
      this._onlyHTML = onlyHTML
46
      this.template = template
47
      this.HbsTemplatePath = path.join(config.root, config.templates.url, 'hbs/'+templateId+'.hbs')
48
49
      abeEngine.instance.content = json
50
      
51
      // This pattern finds all abe tags which are not enclosed in a html tag attribute
52
      // it finds this one: <title>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</title>
53
      // it excludes this one: <meta name="description" content='{{abe type="text" key="meta_description" desc="Meta description" tab="Meta" order="4100"}}"/> 
54
      this.abePattern = /[^"']({{abe.*?type=[\'|\"][text|rich|textarea]+[\'|\"][\s\S].*?}})/g
55
56
      // This pattern finds all abe tags enclosed in a HTML tag attribute
57
      this.abeAsAttributePattern = /( [A-Za-z0-9\-\_]+=["|']{1}{{abe.*?}})/g
58
59
      // This pattern finds all {{#each ...}}...{{/each}} blocks
60
      this.eachBlockPattern = />\s*(\{\{#each (\r|\t|\n|.)*?\/each\}\})/g
61
62
      // This pattern finds all {{#each ...}}...{{/each}} blocks
63
      this.blockPattern = /(\{\{#each.*\}\}[\s\S]*?\{\{\/each\}\})/g
64
65
      // Remove text with attribute "visible=false"
66
      this._removeHidden()
67
    
68
      if(!this._onlyHTML) {
69
70
        // Surrounds each Abe tag (which are text/rich/textarea and not in html attribute) with <abe> tag
71
        // ie. <title><abe>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</abe></title>
72
        this._encloseAbeTag()
73
      }
74
75
      // je rajoute les index pour chaque bloc lié à un each
76
      this._indexEachBlocks()
77
      
78
      if(!this._onlyHTML){
79
80
        // Je maj les attributs associés aux Abe qui sont dans des attributs de tag HTML
81
        this._updateAbeAsAttribute()
82
83
        // je rajoute les attributs pour les tags Abe (qui ne sont pas dans un attribut HTML)
84
        this._updateAbeAsTag()
85
86
        // Don't know what it does...
87
        var source = config.source.name
88
        if(typeof json[source] !== 'undefined' && json[source] !== null) {
89
          var keys = Object.keys(json[source])
90
          
91
          for(var i in keys) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
92
            var replaceEach = new RegExp(`<!-- \\[\\[${keys[i]}\\]\\][\\s\\S]*?-->`, 'g')
93
            this.template = this.template.replace(replaceEach, '')
94
95
            var patAttrSource = new RegExp(' ([A-Za-z0-9\-\_]+)=["|\'].*?({{' + keys[i] + '}}).*?["|\']', 'g')
96
            var patAttrSourceMatch = this.template.match(patAttrSource)
97
            var patAttrSourceInside = null
98
            var patAttrSourceCheck = null
99
            var checkEscaped = null
0 ignored issues
show
Unused Code introduced by
The variable checkEscaped seems to be never used. Consider removing it.
Loading history...
100
101
            if(patAttrSourceMatch != null) {
102
              patAttrSourceInside = new RegExp('(\\S+)=["\']?((?:.(?!["\']?\\s+(?:\\S+)=|[>"\']))+.)["\']?({{' + keys[i] + '}}).*?["|\']', 'g')
103
              Array.prototype.forEach.call(patAttrSourceMatch, (pat) => {
104
                patAttrSourceCheck = patAttrSourceInside.exec(pat)
0 ignored issues
show
Bug introduced by
The variable patAttrSourceInside is changed as part of the for-each loop for example by new RegExp("(\S+)=["']?(...i + "}}).*?["|']", "g") on line 102. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
105
                if(patAttrSourceCheck != null) {
0 ignored issues
show
Bug introduced by
The variable patAttrSourceCheck is changed as part of the for-each loop for example by patAttrSourceInside.exec(pat) on line 104. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
106
                  var checkEscaped = /["|'](.*?)["|']/
107
                  checkEscaped = checkEscaped.exec(patAttrSourceCheck[0])
108
                  if(checkEscaped != null && checkEscaped.length > 0) {
109
                    checkEscaped = escape(checkEscaped[1])
110
                    this.template = this.template.replace(
111
                      patAttrSourceCheck[0],
112
                      ` data-abe-attr="${patAttrSourceCheck[1]}" data-abe-attr-escaped="${checkEscaped}" data-abe="${keys[i]}" ${patAttrSourceCheck[0]}`
0 ignored issues
show
introduced by
The variable i is changed by the for-each loop on line 91. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
113
                    )
114
                  }
115
                }
116
              })
117
            }
118
119
            var eachSource = new RegExp(`({{#each ${keys[i]}}[\\s\\S a-z]*?{{\/each}})`, 'g')
120
            var matches = this.template.match(eachSource)
121
            if(typeof matches !== 'undefined' && matches !== null) {
122
              Array.prototype.forEach.call(matches, (match) => {
123
                this.template = this.template.replace(match, `${match}<!-- [[${keys[i]}]] ${cmsTemplates.encodeAbeTagAsComment(match)} -->`)
0 ignored issues
show
introduced by
The variable i is changed by the for-each loop on line 91. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
124
              })
125
            }
126
          }
127
        }
128
      }
129
     
130
      this._addSource(json)
131
132
      // We remove the {{abe type=data ...}} from the text 
133
      this.template = cmsData.source.removeDataList(this.template)
134
135
      // It's time to replace the [index] by {{@index}} (concerning each blocks)
136
      this.template = this.template.replace(/\[index\]\./g, '{{@index}}-')
137
138
      if(config.files.templates.precompile){
139
        // Let's persist the precompiled template for future use (kind of cache)
140
        fse.writeFileSync(this.HbsTemplatePath, Handlebars.precompile(this.template), 'utf8')
141
        Manager.instance.addHbsTemplate(templateId)
142
      }
143
144
      // I compile the text
145
      var compiledTemplate = Handlebars.compile((!this._onlyHTML) ? cmsTemplates.insertDebugtoolUtilities(this.template) : this.template)
146
147
      // I create the html page ! yeah !!!
148
      this.html = compiledTemplate(json, {data: {intl: config.intlData}})
149
    }
150
151
    if(this._onlyHTML) {
152
      this.html = Hooks.instance.trigger('afterPageSaveCompile', this.html, json)
153
    }else {
154
      this.html = Hooks.instance.trigger('afterPageEditorCompile', this.html, json)
155
    }
156
  }
157
158
  _updateAbeAsAttribute() {
159
    var match
160
    while (match = this.abeAsAttributePattern.exec(this.template)) { // While regexp match {{attribut}}, ex: link, image ...
161
      if(cmsData.regex.isSingleAbe(match[0], this.template)){
162
        var more_attr = ''
163
        var getattr = cmsData.regex.getAttr(match, 'key').replace(/\./g, '-')
164
        this.template = this.template.replace(
165
          new RegExp(match[0]),
166
          ' data-abe-attr-' + cmsData.regex.validDataAbe(getattr) + '="'  + (match[0].split('=')[0]).trim() + '"' +
167
          ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="'  + getattr + '"' +
168
          more_attr + match[0].replace('}}', ' has-abe=1}}')
169
        )
170
      }
171
    }
172
173
    return this
174
  }
175
176
  _updateAbeAsTag() {
177
    var match
178
    while (match = this.abePattern.exec(this.template)) {
179
      var getattr = cmsData.regex.getAttr(match, 'key').replace(/\./g, '-')
180
      this.template = this.template.replace(
181
        cmsData.regex.escapeTextToRegex(match[0], 'g'),
182
        ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="'  + getattr + '" ' + match[0]
183
      )
184
    }
185
186
    return this
187
  }
188
  
189
  /**
190
   * [_indexEachBlocks description]
191
   * @param  {[type]} text   [description]
0 ignored issues
show
Documentation introduced by
The parameter text does not exist. Did you maybe forget to remove this comment?
Loading history...
192
   * @param  {[type]} blocks [description]
0 ignored issues
show
Documentation introduced by
The parameter blocks does not exist. Did you maybe forget to remove this comment?
Loading history...
193
   * @return {[type]}        [description]
194
   */
195
  _indexEachBlocks() {
196
    // create an array of {{each}} blocks
197
    var blocks = this._splitEachBlocks()
198
199
    Array.prototype.forEach.call(blocks, (block) => {
200
      var key = block.match(/#each (.*)\}\}/)
201
      key = key[1]
202
      let util = new cmsEditor.form()
0 ignored issues
show
Unused Code introduced by
The variable util seems to be never used. Consider removing it.
Loading history...
203
      var match
204
205
      if(!this._onlyHTML) {
206
207
        var voidData = {}
208
        voidData[key] = [{}]
209
        var blockCompiled = Handlebars.compile(block.replace(/{{abe (.*?)}}/g, '[[abe $1]]').replace(new RegExp(`\\.\\.\/${config.meta.name}`, 'g'), config.meta.name))
210
        var blockHtml = blockCompiled(voidData, {data: {intl: config.intlData}}).replace(/\[\[abe (.*?)\]\]/g, '{{abe $1}}')
211
212
        // je rajoute un data-abe-block avec index sur tous les tags html du bloc each
213
        var textEachWithIndex = block.replace(/(<(?![\/])[A-Za-z0-9!-]*)/g, '$1 data-abe-block="' + key + '{{@index}}"')
214
215
        // je remplace le block dans le texte par ça
216
        this.template = this.template.replace(block, textEachWithIndex + `<!-- [[${key}]] ${cmsTemplates.encodeAbeTagAsComment(blockHtml)} -->`)
217
      }
218
219
      // Pour chaque tag Abe, je mets en forme ce tag avec des data- supplémentaires
220
      while (match = this.abePattern.exec(block)) {
221
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length)
222
      }
223
224
      // Pour chaque tag Abe attribut de HTML, je mets en forme ce tag avec des data- supplémentaires sur le tag html parent
225
      while (match = this.abeAsAttributePattern.exec(block)) {
226
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length)
227
      }  
228
    })
229
230
    return this
231
  }
232
233
  /**
234
   * create an array of {{#each}} blocks from the html document
235
   * @param  {String} html the html document
0 ignored issues
show
Documentation introduced by
The parameter html does not exist. Did you maybe forget to remove this comment?
Loading history...
236
   * @return {Array}      the array of {{#each}} blocks
237
   */
238
  _splitEachBlocks() {
239
    var block
240
    var blocks = []
241
242
    while (block = this.blockPattern.exec(this.template)) {
243
      blocks.push(block[1])
244
    }
245
246
    return blocks
247
  }
248
249
  _insertAbeEach(theMatch, key, lastIndex) {
0 ignored issues
show
Unused Code introduced by
The parameter lastIndex is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
250
    var matchBlock = theMatch[0]
251
    if(cmsData.regex.isEachStatement(matchBlock)) return
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
252
    if(cmsData.regex.isBlockAbe(matchBlock)){
253
      var matchblockattr = (matchBlock.split('=')[0]).trim()
254
      var getattr = cmsData.regex.getAttr(matchBlock, 'key').replace('.', '[index].')
255
      var newMatchBlock = ((!this._onlyHTML) ?
256
                            (/=[\"\']\{\{(.*?)\}\}/g.test(matchBlock) ?
257
                                ' data-abe-attr-' + cmsData.regex.validDataAbe(getattr) + '="'  + matchblockattr + '"' :
258
                                '') +
259
                            ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="' + getattr + '" ' + matchBlock :
260
                            matchBlock)
261
          .replace(new RegExp('(key=[\'|"])' + key + '.', 'g'), '$1' + key + '[index].')
262
          .replace(/\{\{abe/, '{{abe dictionnary=\'' + key + '\'')
263
264
      this.template = this.template.replace(matchBlock, newMatchBlock)
265
    }
266
267
    return this
268
  }
269
270
  /**
271
   * add <abe> tag around html tag
272
   * @param {String} text html string
0 ignored issues
show
Documentation introduced by
The parameter text does not exist. Did you maybe forget to remove this comment?
Loading history...
273
   */
274
  _removeHidden() {
275
    this.template = this.template.replace(/(\{\{abe.*visible=[\'|\"]false.*\}\})/g, '')
276
277
    return this
278
  }
279
280
  /**
281
   * add <abe> tag around html tag
282
   * @param {String} text html string
0 ignored issues
show
Documentation introduced by
The parameter text does not exist. Did you maybe forget to remove this comment?
Loading history...
283
   */
284
  _encloseAbeTag() {
285
    var match
286
    while (match = this.abePattern.exec(this.template)) {
287
      this.template = this.template.replace(cmsData.regex.escapeTextToRegex(match[1], 'g'), '<abe>' + match[1].trim() + '</abe>')
288
    }
289
290
    return this
291
  }
292
293
  _addSource(json) {
294
    var listReg = /({{abe.*type=[\'|\"]data.*}})/g
295
    var match
296
297
    while (match = listReg.exec(this.template)) {
298
      var editable = cmsData.regex.getAttr(match[0], 'editable')
299
      var key = cmsData.regex.getAttr(match[0], 'key')
300
301
      if((typeof editable === 'undefined' || editable === null || editable === '' || editable === 'false')
302
        && typeof json[config.source.name] !== 'undefined' && json[config.source.name] !== null) {
303
        json[key] = json[config.source.name][key]
304
      }
305
306
      json = Hooks.instance.trigger('afterAddSourcePage', json, match[0])
307
    }
308
  }
309
}